﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using TimbreRecognition.FeatureExtraction;
using TimbreRecognition.Forms;
using TimbreRecognition.Recognition;
using TimbreRecognition.Recognition.ESP;
using TimbreRecognition.Recognition.Model;
using TimbreRecognition.Util;

namespace TimbreRecognition.Desktop
{
    //Idea
    public partial class MainForm : Form
    {
        private const int SampleRate = FeatureConstants.SamplesRate;

        private static readonly string[] Instruments =
        {
           "Trumpet",
           "Violin",
           "Piano",
            "Flute",
           "Clarnet",
         //  "Guitar",
            "Tuba",
           // "Bass",
            "Trombone",
           // "Oboe",
           "Marimba",
          // "Saxophone",
          "Horn",
           // "Cello"
        };

        private readonly FeatureExtractionFacade featureFacade;

        private INetwork network;

        private List<Feature> features;

        private List<CorrespondenceItem> correspondenceMap; 

        public MainForm()
        {
            InitializeComponent();

            featureFacade = new FeatureExtractionFacade(Instruments);
           
        }

        private void browseTrainingSamplesButton_Click(object sender, EventArgs e)
        {
            DialogResult result = folderBrowserDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                this.trainingSamplesPathTextBox.Text = folderBrowserDialog.SelectedPath;
            }
        }
    

        private void trainNetworkButton_Click(object sender, EventArgs e)
        {
          
            string trainigDirectory = this.trainingSamplesPathTextBox.Text;
            TrainingSet trainingSet = featureFacade.ExtractForTraining(trainigDirectory);

            NeuralNetworkBuilder networkBuilder = new NeuralNetworkBuilder();
            networkBuilder.TrainingData = trainingSet.TrainingData;
            networkBuilder.ValidationData = trainingSet.ValidationData;
            ILogger logger = new TextBoxLogger(this.logTextBox);
            networkBuilder.Logger = logger;

            network = networkBuilder.Build();

            this.recognizeButton.Enabled = true;
            this.recognitionToolStripMenuItem.Enabled = true;

            Dictionary<double[], double[]> testData = trainingSet.TestData.ToDictionary(o => o.DataSeries,
                o => o.ExpectedOutput);
            ShowTestResult(testData);
        }

        private void browseFileButton_Click(object sender, EventArgs e)
        {
            DialogResult result = openFileDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                this.fileToRecognizePathTextBox.Text = openFileDialog.FileName;
            }
        }

        private void recognizeButton_Click(object sender, EventArgs e)
        {
            this.testResultListBox.Items.Clear();

            string fileToRecognize = this.fileToRecognizePathTextBox.Text;
            features = featureFacade.ExtractFeaturesFromFile(fileToRecognize);

            FileInfo fileInfo = new FileInfo(fileToRecognize);
            double[] expectedResult = new double[Instruments.Length];
            for (int i = 0; i < Instruments.Length; i++)
            {
                if (fileInfo.Name.StartsWith(Instruments[i]))
                {
                    expectedResult[i] = 1;
                }
            }

            List<DataItem> dataItems = featureFacade.ExtractFromFile(fileToRecognize);
            Dictionary<double[], double[]> testData = dataItems.ToDictionary(o => o.DataSeries, o => expectedResult);

            ResultTester tester = new ResultTester();
            double error = tester.GetErrorNumber(testData, network);
            this.resultAccuracySingleLabel.Text = ((1 - error) * 100).ToString("F2") + '%';

            NeuronNetworkWorker networkHelper = new NeuronNetworkWorker(network);
            List<double[]> classes = networkHelper.ClassifyBatch(dataItems.Select(o=>o.DataSeries));
            List<double[]> output = networkHelper.GetOutoutBatch(dataItems.Select(o => o.DataSeries));

            ShowResult(classes, output);
        }

        private void ShowResult(List<double[]> classList, List<double[]> outputLits)
        {          

            for (int i = 0; i < features.Count; i++)
            {
                string text = PresenterHelper.FormatNetworkOutput(classList[i], outputLits[i]);            
                testResultListBox.Items.Add(text);
            }
        }

        private void testResultListBox_DoubleClick(object sender, EventArgs e)
        {
            mfccMenuItem_click(sender, e);    
        }

        private void testResultListBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Right)
            {
                return;
            }

            testResultListBox.SelectedIndex = testResultListBox.IndexFromPoint(e.Location);
            if (testResultListBox.SelectedIndex != -1)
            {
                this.listContextMenuStrip.Show();
            }
        }

        private void mfccMenuItem_click(object sender, System.EventArgs e)
        {
            const int LineWidth = 3;

            int index = this.testResultListBox.SelectedIndex;
            if (index >= 0)
            {
                ChartForm chartForm = new ChartForm(features[index].MFCC) { LineWidth = LineWidth };
                chartForm.ShowDialog();
            }
        }

        private void spectrumMenuItem_click(object sender, System.EventArgs e)
        {
            int index = this.testResultListBox.SelectedIndex;
            if (index >= 0)
            {
                double[] frequencies = GetFrequencies(features[index].PowerSpectrum.Length);
                ChartForm chartForm = new ChartForm(features[index].PowerSpectrum, frequencies);
                chartForm.ShowDialog();
            }
        }

        private double[] GetFrequencies(int dataLenght)
        {
            double[] frequencies = new double[dataLenght];

            for (int i = 0; i < dataLenght; i++)
            {
                frequencies[i] = (double)(i * SampleRate) / (2 * (dataLenght - 1));
            }

            return frequencies;
        }

        private void recognizeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            RecognitionForm form = new RecognitionForm(network, Instruments);
            form.ShowDialog();
        }

        private void unloadButton_Click(object sender, EventArgs e)
        {
            string trainigDirectory = this.trainingSamplesPathTextBox.Text;
            List<Feature> trainingFeatures = featureFacade.ExtractFeaturesFromDirectory(trainigDirectory);
          //  FeatureNormalizer normalizer = FeatureNormalizer.GetInstance();
         //   List<Feature> normalized = normalizer.Normalize(trainingFeatures);

            DialogResult dialogResult = saveFileDialog.ShowDialog();
            if (dialogResult == DialogResult.OK)
            {
                FeatureWriter writer = new FeatureWriter();
                writer.Write(saveFileDialog.FileName, trainingFeatures);

                MessageBox.Show("Выгрузка успешно завершена");
            }            
        }

        private void kohenenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            KohenenMapForm form = new KohenenMapForm(this.trainingSamplesPathTextBox.Text, Instruments);
            form.Show();
        }

        private void browseTestFolderButton_Click(object sender, EventArgs e)
        {
            DialogResult result = folderBrowserDialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                this.testFolderTextBox.Text = folderBrowserDialog.SelectedPath;
            }
        }

        private void testBatchButton_Click(object sender, EventArgs e)
        {
            string testDirectory = this.testFolderTextBox.Text;
            List<DataItem> dataItems = featureFacade.ExtractForClassification(testDirectory);
            Dictionary<double[], double[]> testData = dataItems.ToDictionary(o => o.DataSeries, o => o.ExpectedOutput);
            ShowTestResult(testData);
        }

        private void ShowTestResult(Dictionary<double[], double[]> testData)
        {
            instruemntsListBox.Items.Clear();

            ResultTester tester = new ResultTester();
            double error = tester.GetErrorNumber(testData, network);
            this.resultAccuracyLabel.Text = ((1 - error) * 100).ToString("F2") + '%';

            correspondenceMap = tester.GetExpectedToActualCorrespondenceMap(testData, network);

            for (int i = 0; i < Instruments.Length; i++)
            {
                double accuracy = 100 * (double)correspondenceMap.Count(o => o.Expected[i] == 1.0 && o.Actual[i] == 1.0) /
                                  correspondenceMap.Count(o => o.Expected[i] == 1.0);
                this.instruemntsListBox.Items.Add(Instruments[i] + " = " + accuracy.ToString("F2") + "%");
            }
        }

        private void unloadAsTextFileButton_Click(object sender, EventArgs e)
        {
            string trainigDirectory = this.trainingSamplesPathTextBox.Text;
            List<DataItem> trainingFeatures = featureFacade.ExtractFromDirectory(trainigDirectory);

            DialogResult dialogResult = saveFileDialog.ShowDialog();
            if (dialogResult == DialogResult.OK)
            {
                DataItemWritter writer = new DataItemWritter();
                writer.Write(saveFileDialog.FileName, trainingFeatures);

                MessageBox.Show("Выгрузка успешно завершена");
            }            

        }

        private void plotDataButton_Click(object sender, EventArgs e)
        {
            string trainigDirectory = this.trainingSamplesPathTextBox.Text;
            List<DataItem> trainingFeatures = featureFacade.ExtractFromDirectory(trainigDirectory);

            Dictionary<double[], int> data = trainingFeatures.ToDictionary(o =>
                {
                    double[] values = new double[3];
                    Array.Copy(o.DataSeries, values, 3);
                    return values;
                },
                o => Array.IndexOf(o.ExpectedOutput, 1.0));

            List<Statictics> statisticsData = new List<Statictics>();
            for (int i = 0; i < Instruments.Length; i++)
            {
                List<double[]> points = data.Where(o => o.Value == i).Select(o => o.Key).ToList();
                Statictics statictics = StatisticsUtil.GetStatistics(points);
                statisticsData.Add(statictics);
            }

            StringBuilder text = new StringBuilder();
            double totalSeperationIndex = 0;
            for (int i = 0; i < statisticsData.Count; i++)
            {
                Statictics statictics = statisticsData[i];
                text.Append(">>>>");
                text.AppendLine(Instruments[i]);
                text.Append("mean = (");

                foreach (double value in statictics.Mean)
                {
                    text.Append(value.ToString("F2"));
                    text.Append("; ");
                }

                text.AppendLine(")");
               // text.AppendLine();

                text.Append("Spread = ");
                text.AppendLine(statictics.Spread.ToString("F2"));

                for (int j = 0; j < statisticsData.Count; j++)
                {
                    if (i != j)
                    {
                        Statictics currecntStatictics = statisticsData[j];
                        double distance = StatisticsUtil.GetDistance(statictics.Mean, currecntStatictics.Mean);

                        text.Append("distance (");
                        text.Append(i);
                        text.Append(", ");
                        text.Append(j);
                        text.Append(") => ");
                        text.AppendLine(distance.ToString("F2"));

                        text.Append("Separation Index = ");
                        double seperationIndex = distance/(statictics.Spread + currecntStatictics.Spread);

                        totalSeperationIndex += seperationIndex;

                        text.AppendLine(seperationIndex.ToString("F2"));
                    }
                }
            }

            text.Append("Total separation index = ");
            text.AppendLine(totalSeperationIndex.ToString("F2"));

            this.statisticalDataTextBox.Text = text.ToString();

            Chart3DForm chart = new Chart3DForm(data);
            chart.ShowDialog();
        }

        private void trainEspButton_Click(object sender, EventArgs e)
        {

            string trainigDirectory = this.trainingSamplesPathTextBox.Text;
            TrainingSet trainingSet = featureFacade.ExtractForTraining(trainigDirectory);

            ESPNetworkCreator creator = new ESPNetworkCreator();
            creator.Logger = new TextBoxLogger(this.logTextBox);

            network = creator.Create(trainingSet);
        }

        private void instruemntsListBox_SelectedIndexChanged(object sender, EventArgs e)
        {

            int selectedIndex = this.instruemntsListBox.SelectedIndex;
            if (selectedIndex >= 0 && correspondenceMap != null)
            {
                StringBuilder text = new StringBuilder();

                for (int i = 0; i < Instruments.Length; i ++)
                {
                    int count = correspondenceMap.Count(o => o.Expected[selectedIndex] == 1.0 && o.Actual[i] == 1.0);
                    text.Append(Instruments[i]);
                    text.Append(" = ");
                    text.Append(count);
                    text.AppendLine();
                }

                this.recognitionByInstrumentsTextBox.Text = text.ToString();
            }

        }
    }    

    public class TextBoxLogger : ILogger
    {
        private TextBox textBox;

        public TextBoxLogger(TextBox textBox)
        {
            this.textBox = textBox;
        }

        public void Log(string message)
        {
            textBox.AppendText(message);
            textBox.AppendText(Environment.NewLine);
        }
    }
}
